package com.gk.springauth2auth.config.jwt;

import com.gk.springauth2auth.service.UserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * AuthorizationServer配置的适配类,oauth2认证服务器
 * 内存模式
 * 配置信息：
 *      客户端认证信息-令牌暴露地址以及安全约束
 * 使用jwt令牌
 *
 * @Author poison
 * @Date 2020/10/10 15:54
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigJwt extends AuthorizationServerConfigurerAdapter {

    /**
     * 认证管理器，密码模式需要自定义
     */
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private UserDetailService userDetailService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 从数据库中获取客户端信息
     * @return
     */
    @Bean
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        jdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return jdbcClientDetailsService;
    }

    /**
     * 配置从哪里获取ClientDetails信息。
     * 配置客户端详情信息
     * 在client_credentials授权方式下，只要这个ClientDetails信息。
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //认证信息从数据库获取
        clients.withClientDetails(clientDetails());

    }

    /**
     * <p>注意，自定义TokenServices的时候，需要设置@Primary，否则报错，</p>
     * 令牌访问服务
     * @return
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        // 客户端信息服务
        tokenServices.setClientDetailsService(clientDetailsService);
        // 令牌存储策略
        tokenServices.setTokenStore(tokenStore);

        // 设置令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        tokenServices.setTokenEnhancer(tokenEnhancerChain);

        // 令牌刷新时间
        tokenServices.setSupportRefreshToken(true);
        // token有效期自定义设置，默认12小时
        tokenServices.setAccessTokenValiditySeconds(60*5);
        // refresh_token默认30天
        tokenServices.setRefreshTokenValiditySeconds(60*5);
        return tokenServices;
    }

    @Override
    /**
     * 用来配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)
     * 令牌端点可理解为申请令牌的地址。令牌服务的意思就是怎么去发放令牌。
     * 注入相关配置：
     * 1. 密码模式下配置认证管理器 AuthenticationManager
     * 2. 设置 AccessToken的存储介质tokenStore， 默认使用内存当做存储介质。
     * 3. userDetailsService注入
     */

    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                // token的保存方式
                .tokenStore(tokenStore)
                // 密码模式需要
                .authenticationManager(authenticationManager)
                // 允许post的提交访问令牌
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                // MUST：密码模式下需设置一个AuthenticationManager对象,获取 UserDetails信息
                .userDetailsService(userDetailService)
                // 授权码模式需要
                .authorizationCodeServices(authorizationCodeServices);

        // 令牌管理服务
        endpoints.tokenServices(defaultTokenServices());
    }

    @Bean
    public JdbcAuthorizationCodeServices authorizationCodeServices() {
        //设置授权码模式的授权码如何存取，使用数据库方式
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     *   用来配置令牌端点(Token Endpoint)的安全约束.
     *   配置：安全检查流程,用来配置令牌端点（Token Endpoint）的安全与权限访问
     *   表明不是所有令牌请求都可以访问
     * 	 默认过滤器：BasicAuthenticationFilter
     * 	 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
     * 	 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤，默认值：denyAll()
     * 	 对以下的几个端点进行权限配置：
     * 	 /oauth/authorize：授权端点
     * 	 /oauth/token：令牌端点
     * 	 /oauth/confirm_access：用户确认授权提交端点
     * 	 /oauth/error：授权服务错误信息端点
     * 	 /oauth/check_token：用于资源服务访问的令牌解析端点
     * 	 /oauth/token_key：提供公有密匙的端点，如果使用JWT令牌的话
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()//允许客户表单认证
                //.checkTokenAccess("isAuthenticated()")
                // 检测令牌，设置为公开
                .checkTokenAccess("permitAll()")
                .tokenKeyAccess("permitAll()");
    }


}
